Architecture Diagram
# Architecture Diagram
Phase 1 — ExzenTCG Homelab
Network Topology¶
flowchart TB
subgraph Internet
USER["User Browser"]
CF["Cloudflare Edge<br/>TLS termination"]
CFZT["Cloudflare Zero Trust<br/>Access Policy"]
WEBHOOK["External Webhooks<br/>(Stripe, Telegram, etc.)"]
end
subgraph Proxmox["Proxmox VE (192.168.0.200)"]
subgraph CT101["CT 101 — edge-gateway (192.168.0.51)"]
CFLRD["cloudflared<br/>:7844 → CF edge"]
NPM["Nginx Proxy Manager<br/>:80 (localhost only)<br/>:81 (admin, LAN)"]
end
subgraph CT102["CT 102 — n8n-app (192.168.0.52)"]
N8N["n8n v1.80.0<br/>:5678 (LAN only)"]
end
subgraph CT103["CT 103 — homepage (192.168.0.53)"]
DASH["Homepage Dashboard<br/>:3000"]
end
AMT["Intel AMT<br/>:16992"]
PVFW["Proxmox Firewall<br/>Per-CT rules + IP sets"]
end
subgraph Admin["Admin Access"]
DESKTOP["Admin Desktop<br/>192.168.0.16"]
LAPTOP["Admin Laptop<br/>via Tailscale"]
TS["Tailscale Mesh<br/>100.89.70.63<br/>Subnet: 192.168.0.0/24"]
end
ROUTER["Home Router (D-Link)<br/>192.168.0.1<br/>DNS / DHCP / NAT"]
%% Traffic flow
USER -->|"HTTPS :443"| CF
WEBHOOK -->|"HTTPS :443"| CF
CF -->|"Access check"| CFZT
CFZT -->|"Tunnel (TCP :7844)"| CFLRD
CFLRD -->|"HTTP localhost:80"| NPM
NPM -->|"HTTP :5678"| N8N
NPM -->|"HTTP :3000"| DASH
%% Homepage widget connections
DASH -.->|"API :8006"| Proxmox
DASH -.->|"API :81"| NPM
DASH -.->|"API :5678"| N8N
%% Admin access
DESKTOP -->|":81 NPM admin"| NPM
DESKTOP -->|":22 SSH"| N8N
DESKTOP -->|":8006 WebUI"| Proxmox
LAPTOP -->|"WireGuard"| TS
TS -->|":8006 WebUI"| Proxmox
TS -->|"Subnet route"| ROUTER
TS -->|"Subnet route"| AMT
%% DNS
CT101 -->|"UDP :53"| ROUTER
CT102 -->|"UDP :53"| ROUTER
CT103 -->|"UDP :53"| ROUTER
%% Firewall
PVFW -.->|"enforces"| CT101
PVFW -.->|"enforces"| CT102
PVFW -.->|"enforces"| CT103
style CF fill:#f38020,color:#fff
style CFZT fill:#f38020,color:#fff
style CT101 fill:#1a3a4a,color:#fff
style CT102 fill:#1a3a4a,color:#fff
style CT103 fill:#1a3a4a,color:#fff
style N8N fill:#ea4b71,color:#fff
style DASH fill:#059669,color:#fff
style TS fill:#4c8bf5,color:#fff
style AMT fill:#555,color:#fff
Traffic Flow (request lifecycle)¶
n8n.exzentcg.com / dash.exzentcg.com:
1. User visits https://n8n.exzentcg.com (or dash.exzentcg.com)
2. DNS resolves → CNAME → cfargotunnel.com → Cloudflare edge IP
3. Cloudflare terminates TLS
4. Cloudflare Zero Trust checks Access policy
→ /webhook/* paths: bypass (no auth)
→ all other paths: require email login (Google OAuth or one-time PIN)
5. Authenticated request enters Cloudflare Tunnel
6. cloudflared (CT 101, network_mode: host) receives on TCP 7844
7. cloudflared forwards to http://localhost:80 (NPM)
8. NPM matches Host header → routes to correct backend:
→ n8n.exzentcg.com → http://192.168.0.52:5678 (n8n)
→ dash.exzentcg.com → http://192.168.0.53:3000 (Homepage)
9. Backend processes request and responds
10. Response travels back through the same chain
Remote admin (Tailscale):
1. Laptop connects to Tailnet (WireGuard)
2. Tailscale subnet route (192.168.0.0/24) advertised by Proxmox host
3. All LAN devices reachable: Proxmox :8006, Router :80, AMT :16992, NPM :81
4. Tailscale traffic bypasses Proxmox firewall (ts-input chain, see D3)
Firewall Boundaries¶
┌─────────────────────────────┐
│ INTERNET (any) │
└─────────┬───────────────────-┘
│
┌─────────▼───────────────────-┐
│ Cloudflare Edge + Tunnel │
│ (TLS + Access + WAF) │
└─────────┬───────────────────-┘
│ TCP 7844
┌───────────────▼───────────────────-┐
│ CT 101 — edge-gateway │
│ │
│ IN: localhost:80,443 only │
│ +admin_desktop:81 │
│ CT 103:81 (widget) │
│ everything else: DROP │
│ │
│ OUT: router:53 (DNS) │
│ .52:5678 (n8n) │
│ .53:3000 (homepage) │
│ DROP +lan_subnet ◄── key! │
│ 7844 (tunnel) │
│ 443, 80 (APIs) │
└──────┬────────────┬───────────────-┘
│ TCP 5678 │ TCP 3000
┌──────▼──────┐ ┌──▼──────────────-─┐
│ CT 102 │ │ CT 103 │
│ n8n-app │ │ homepage │
│ │ │ │
│ IN: │ │ IN: │
│ +edge:5678 │ │ +edge:3000 │
│ +admin:22 │ │ +admin:22,3000 │
│ CT103:5678 │ │ DROP │
│ +admin:5678│ │ │
│ DROP │ │ OUT: │
│ │ │ .200:8006 (PVE) │
│ OUT: │ │ .51:81 (NPM) │
│ router:53 │ │ .52:5678 (n8n) │
│ DROP +lan │ │ DROP +lan ◄─ key! │
│ 443 only │ │ 443 (HTTPS) │
└─────────────┘ └───────────────────-┘
Important
The DROP +lan_subnet rule before ACCEPT :443 is the critical lateral movement prevention. It ensures that even if a container is compromised, it cannot reach other LAN devices (desktop, router admin, other containers) — even on port 443.
IP Address Map¶
| IP | Role | Access |
|---|---|---|
| 192.168.0.1 | Home router | DNS/DHCP/NAT |
| 192.168.0.16 | Admin desktop | In admin_desktop IP set |
| 192.168.0.51 | edge-gateway (CT 101) | NPM + cloudflared |
| 192.168.0.52 | n8n-app (CT 102) | n8n only |
| 192.168.0.53 | homepage (CT 103) | Homepage dashboard |
| 192.168.0.54 | ollama (CT 104) | Ollama + Open WebUI |
| 192.168.0.55 | tcg-staging (CT 105) | Medusa staging backend |
| 192.168.0.56 | exzen-staging (CT 106) | exzen-core FastAPI staging (Telegram bots in long-poll mode) |
| 192.168.0.57 | dev-shell (CT 107) | Node 22 + Docker dev environment; Claude Code installed |
| 192.168.0.58 | shopee-print (CT 108) | shopee-auto-print service; USB passthrough to Fujun 9250 thermal printer |
| 192.168.0.200 | Proxmox host | Hypervisor |
| 192.168.0.200:16992 | Intel AMT | Out-of-band management (MeshCommander) |
| 100.89.70.63 | Proxmox via Tailscale | Remote admin + subnet route (192.168.0.0/24) |
| 100.121.171.73 | telebot-prod via Tailscale | Oracle Cloud production exzen-core (Tailscale SSH enabled) |